﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DataStuctureStudy.Deques
{
    public class Deque<T>
    {
        private T[] _items = new T[0];
        private int _size = 0;
        private int _head = -1;
        private int _tail = -1;

        private void AllocateNewArray()
        {
            int newLength = (_size == 0) ? 4 : _size * 2;

            T[] newArray = new T[newLength];

            if (_size > 0)
            {
                // copy contents...
                // if the array has no wrapping, just copy the valid range
                // else copy from head to end of the array and then from 0 to the tail

                // if tail is less than head we've wrapped
                var newIndex = 0;
                if (_tail < _head)
                {
                    // copy the _items[head].._items[end] -> newArray[0]..newArray[N]
                    for (int index = _head; index < _items.Length; index++)
                    {
                        newArray[newIndex] = _items[index];
                        newIndex++;
                    }

                    // copy _items[0].._items[tail] -> newArray[N+1]..
                    for (int index = 0; index <= _tail; index++)
                    {
                        newArray[newIndex] = _items[index];
                        newIndex++;
                    }
                }
                else
                {
                    // copy the _items[head].._items[tail] -> newArray[0]..newArray[N]
                    for (int index = _head; index <= _tail; index++)
                    {
                        newArray[newIndex] = _items[index];
                        newIndex++;
                    }
                }
                _head = 0;
                _tail = _size - 1; // compensate for the extra bump
            }
            else
            {
                // nothing in the array
                _head = -1;
                _tail = -1;
            }

            _items = newArray;
        }

        public void EnqueueFirst(T item)
        {
            // if the array needs to grow
            if (_items.Length == _size)
            {
                AllocateNewArray();
            }

            // since we know the array isn't full and _head is greater than 0
            // we know the slot in front of head is open
            if (_head > 0)
            {
                _head--;
            }
            else
            {
                // otherwise we need to wrap around to the end of the array
                _head = _items.Length - 1;
                if (_tail == -1)
                {
                    _tail = _head;
                }
            }
            _items[_head] = item;
            _size++;
        }

        public void EnqueueLast(T item)
        {
            // if the array needs to grow
            if (_items.Length == _size)
            {
                AllocateNewArray();
            }

            // now we have a properly sized array and can focus on wrapping issues.
            // if _tail is at the end of the array we need to wrap around
            if (_tail == _items.Length - 1)
            {
                _tail = 0;
            }
            else
            {
                _tail++;

                if (_head == -1)
                {
                    _head = _tail;
                }
            }
            _items[_tail] = item;
            _size++;
        }

        public T DequeueFirst()
        {
            if (_size == 0)
            {
                throw new InvalidOperationException("The deque is empty");
            }

            T value = _items[_head];

            if (_head == _items.Length - 1)
            {
                // if the head is at the last index in the array - wrap around.
                _head = 0;
            }
            else
            {
                // move to the next slot
                _head++;
            }
            _size--;
            return value;
        }

        public T DequeueLast()
        {
            if (_size == 0)
            {
                throw new InvalidOperationException("The deque is empty");
            }

            T value = _items[_tail];

            if (_tail == 0)
            {
                // if the tai is at the first index in the array - wrap around.
                _tail = _items.Length - 1;
            }
            else
            {
                // move to the previous slot
                _tail--;
            }
            _size--;
            return value;
        }

        public T PeekFirst()
        {
            if (_size == 0)
            {
                throw new InvalidOperationException("The deque is empty");
            }
            return _items[_head];
        }

        public T PeekLast()
        {
            if (_size == 0)
            {
                throw new InvalidOperationException("The deque is empty");
            }
            return _items[_tail];
        }

        public int Count
        {
            get
            {
                return _size;
            }
        }
    }
}
